////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   SolidBrushObject.cpp
//  Version:     v1.00
//  Created:     16/9/2004 by Timur.
//  Compilers:   Visual Studio.NET
//  Description: 
// -------------------------------------------------------------------------
//  History:
//	03/03/2010	Refactored by Jaesik.
//	01/04/2010	A function of merging solids is added by Jaesik.
//	12/04/2010  Improved visibility to drawing 2D box below measurement's helper
//							text and making edge's line render thicker by Jaesik.
//	21/04/2010	CSG is applied by Jaesik.
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "SolidBrushObject.h"

#include "Objects\SubObjSelection.h"

#include "Viewport.h"

#include "SolidBrushCreatePanel.h"
#include "SolidBrushPanel.h"
#include "SolidBrushSubObjPanel.h"
#include "Brush.h"
#include "BrushCSGCompiler.h"

#include "Material\MaterialManager.h"
#include "Material/Material.h"

#include "PropertiesPanel.h"
#include "ISubObjectSelectionReferenceFrameCalculator.h"

#include <I3Dengine.h>
#include <IEntitySystem.h>
#include <IEntityRenderState.h>
#include <IPhysics.h>

#include <INavigationSystem.h>

#include <GameEngine.h>

#include "SegmentedWorld/SegmentedWorldManager.h"

#define MIN_BOUNDS_SIZE 0.01f

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//! Undo object for Editable Mesh.
class CUndoSolidBrushObject : public IUndoObject
{
public:
	CUndoSolidBrushObject( CSolidBrushObject * obj, SBrush *pBrush,const char *undoDescription )
	{
		// Stores the current state of this object.
		assert( pBrush != 0 );
		if (undoDescription)
			m_undoDescription = undoDescription;
		else
			m_undoDescription = "SolidBrushUndo";

		m_undo = SBrush::CreateBrush(obj);
		m_redo = SBrush::CreateBrush(obj);

		*m_undo = *pBrush;
		m_pBrush = pBrush;
		m_obj = obj;
		m_undoPos = m_obj->GetPos();
	}

	virtual ~CUndoSolidBrushObject()
	{
		delete m_undo;
		delete m_redo;
	}

protected:
	virtual int GetSize()
	{
		// sizeof(undoMesh) + sizeof(redoMesh);
		return sizeof(*this);
	}
	virtual const char* GetDescription() { return m_undoDescription; };

	virtual void Undo( bool bUndo )
	{
		if (bUndo)
		{
			if(gEnv->pAISystem && gEnv->pAISystem->GetNavigationSystem())
			{
				gEnv->pAISystem->GetNavigationSystem()->WorldChanged(m_pBrush->GetWorldBound());
			}

			*m_redo = *m_pBrush;
			m_redoPos = m_obj->GetPos();
		}
		// Undo object state.
		*m_pBrush = *m_undo;
		m_pBrush->UpdateMesh();
		m_obj->SetPos(m_undoPos);
		m_obj->InvalidateBrush();
		m_obj->InvalidateTM(0);
	}
	virtual void Redo()
	{
		*m_pBrush = *m_redo;
		m_pBrush->UpdateMesh();
		m_obj->SetPos(m_redoPos);
		m_obj->InvalidateBrush();
		m_obj->InvalidateTM(0);
	}

private:
	CString m_undoDescription;
	_smart_ptr<SBrush> m_pBrush;
	SBrush* m_undo;
	SBrush* m_redo;
	Vec3 m_undoPos;
	Vec3 m_redoPos;
	_smart_ptr<CSolidBrushObject> m_obj;
};




//////////////////////////////////////////////////////////////////////////
// CBase implementation.
//////////////////////////////////////////////////////////////////////////
IMPLEMENT_DYNCREATE(CSolidBrushObject,CBaseObject)

//////////////////////////////////////////////////////////////////////////
class CSolidFlagsPanelUI : public CPropertiesPanel
{
public:
	_smart_ptr<CVarBlock> pVarBlock;
	_smart_ptr<CSolidBrushObject> m_pObject;

	CSmartVariable<bool> mv_outdoor;
	CSmartVariable<bool> mv_castShadows;
	CSmartVariable<bool> mv_supportSecVisArea;
	CSmartVariable<bool> mv_castLightmap;
	//	CSmartVariable<bool> mv_recvLightmap;
	CSmartVariable<bool> mv_goodOccluder;
	CSmartVariable<bool> mv_rainOccluder;
	CSmartVariable<bool> mv_hideable;
	CSmartVariable<int> mv_ratioViewDist;
	CSmartVariable<int> mv_integrationType;
	CSmartVariable<bool> mv_excludeFromTriangulation;
	CSmartVariable<bool> mv_noStaticDecals;
	CSmartVariable<float> mv_lightmapQuality;

	CSolidFlagsPanelUI()
	{
		pVarBlock = new CVarBlock;

		mv_outdoor = false;
		mv_castShadows = false;
		mv_supportSecVisArea = false;
		mv_castLightmap = false;
		//		mv_recvLightmap = false;
		mv_hideable = false;
		mv_goodOccluder = false;
		mv_rainOccluder = true;
		mv_ratioViewDist = 100;
		mv_integrationType = 0;
		mv_integrationType->SetLimits( 0, 3 );
		mv_integrationType->SetDescription("0-None\n1-SceneMerging\n2-VoxelMesh\n3-VoxelVolume");
		mv_excludeFromTriangulation = false;
		mv_lightmapQuality = 1;
		mv_lightmapQuality->SetLimits( 0,100 );
		mv_ratioViewDist->SetLimits( 0,255 );

		pVarBlock->AddVariable( mv_castShadows,"Cast Shadows" );
		pVarBlock->AddVariable( mv_supportSecVisArea,"SupportSecondVisarea" );
		pVarBlock->AddVariable( mv_outdoor,"Outdoors Only" );
		pVarBlock->AddVariable( mv_goodOccluder,"Good Occluder" );
		pVarBlock->AddVariable( mv_rainOccluder,"Rain Occluder" );
		pVarBlock->AddVariable( mv_ratioViewDist,"View Distance Ratio" );
		pVarBlock->AddVariable( mv_integrationType,"MeshIntegrationType" );
		pVarBlock->AddVariable( mv_excludeFromTriangulation,"AI Exclude From Triangulation" );
		pVarBlock->AddVariable( mv_hideable,"AI Hideable" );
		pVarBlock->AddVariable( mv_noStaticDecals,"No Static Decals" );
	}
	void AddVariables()
	{
		SetVarBlock( pVarBlock,functor(*this,&CSolidFlagsPanelUI::OnVarChange) );
	}
	void SetObject( CSolidBrushObject *pObject )
	{
		m_pObject = pObject;
		if (pObject)
		{
			DeleteVars();

			mv_lightmapQuality = pObject->GetLMQuality();
			mv_ratioViewDist = pObject->GetViewDistRatio();

			int flags = pObject->GetRenderFlags();
			mv_outdoor = (flags&ERF_OUTDOORONLY) != 0;
			mv_goodOccluder = (flags&ERF_GOOD_OCCLUDER) != 0;
			mv_rainOccluder = (flags&ERF_RAIN_OCCLUDER) != 0;

			mv_castShadows = 	(flags&ERF_CASTSHADOWMAPS) != 0;
			mv_supportSecVisArea = (flags&ERF_REGISTER_BY_BBOX) != 0;

			mv_integrationType = 0;
			if(flags&ERF_INTEGRATION_TYPE_BIT_1)
				mv_integrationType = mv_integrationType + 1;
			if(flags&ERF_INTEGRATION_TYPE_BIT_2)
				mv_integrationType = mv_integrationType + 2;

			mv_castLightmap = (flags&ERF_CASTSHADOWINTORAMMAP) != 0;
			//			mv_recvLightmap = (flags&ERF_USERAMMAPS) != 0;
			mv_hideable = (flags&ERF_HIDABLE) != 0;
			mv_noStaticDecals = (flags&ERF_NO_DECALNODE_DECALS) != 0;
			mv_excludeFromTriangulation = (flags&ERF_EXCLUDE_FROM_TRIANGULATION) != 0;

			AddVariables();
		}
	}
	void ModifyFlag( int &nFlags,int flag,CSmartVariable<bool> &var,IVariable *pVar )
	{
		if (var.GetVar() == pVar)
			nFlags = (var) ? (nFlags | flag) : (nFlags & (~flag));
	}
	void OnVarChange( IVariable *pVar )
	{
		CSelectionGroup *selection = GetIEditor()->GetSelection();
		for (int i = 0; i < selection->GetCount(); ++i)
		{
			CBaseObject *pObj = selection->GetObject(i);
			if (pObj->IsKindOf(RUNTIME_CLASS(CSolidBrushObject)))
			{
				CSolidBrushObject *pSolid = static_cast<CSolidBrushObject*>(pObj);
				int nFlags = pSolid->GetRenderFlags();
				ModifyFlag( nFlags,ERF_OUTDOORONLY,mv_outdoor,pVar );
				ModifyFlag( nFlags,ERF_GOOD_OCCLUDER,mv_goodOccluder,pVar );
				ModifyFlag( nFlags,ERF_RAIN_OCCLUDER,mv_rainOccluder,pVar );
				ModifyFlag( nFlags,ERF_CASTSHADOWMAPS,mv_castShadows,pVar );
				ModifyFlag( nFlags,ERF_REGISTER_BY_BBOX,mv_supportSecVisArea,pVar );
				ModifyFlag( nFlags,ERF_CASTSHADOWINTORAMMAP,mv_castLightmap,pVar );
				//				ModifyFlag( nFlags,ERF_USERAMMAPS,mv_recvLightmap,pVar );
				ModifyFlag( nFlags,ERF_HIDABLE,mv_hideable,pVar );
				ModifyFlag( nFlags,ERF_EXCLUDE_FROM_TRIANGULATION,mv_excludeFromTriangulation,pVar );
				ModifyFlag( nFlags,ERF_NO_DECALNODE_DECALS,mv_noStaticDecals,pVar );

				if (mv_lightmapQuality.GetVar() == pVar)
					pSolid->SetLMQuality( mv_lightmapQuality );
				if (mv_ratioViewDist.GetVar() == pVar)
					pSolid->SetViewDistRatio( mv_ratioViewDist );
				if (mv_integrationType.GetVar() == pVar)
				{
					int nVal = mv_integrationType;

					if(nVal&1)
						nFlags |= ERF_INTEGRATION_TYPE_BIT_1;
					else
						nFlags &= ~ERF_INTEGRATION_TYPE_BIT_1;

					if(nVal&2)
						nFlags |= ERF_INTEGRATION_TYPE_BIT_2;
					else
						nFlags &= ~ERF_INTEGRATION_TYPE_BIT_2;
				}

				pSolid->SetRenderFlags( nFlags );

				// need to do this to apply ERF_EXCLUDE_FROM_TRIANGULATION flag
				SBrush *pBrush(	NULL);
				if (pSolid->GetBrush(pBrush))
				{
					IStatObj *pGeom = NULL;
					if( pBrush->GetIStatObj(pGeom) )
					{
						pGeom->SetFilePath( pSolid->GenerateGameFilename() );
						Matrix34A mtx = pSolid->GetWorldTM();
						pSolid->GetEngineNode()->SetEntityStatObj( 0,pGeom, &mtx );
					}
				}
			}
		}
	}
};

namespace
{
	CSolidBrushPanel* s_brushPanel = NULL;
	int s_brushPanelId = 0;

	CSolidBrushCreatePanel* s_brushPanelCreate = NULL;
	int s_brushPanelCreateId = 0;

	CSolidFlagsPanelUI* s_brushPanelFlags = NULL;
	int s_brushPanelFlagsId = 0;

	CSolidBrushSubObjPanel* s_brushPanelSubObj = NULL;
	int s_brushPanelSubObjId = 0;
}


//////////////////////////////////////////////////////////////////////////
class CSolidBrushMouseCreateCallback : public IMouseCreateCallback
{
	CSolidBrushObject *m_pObject;
	CPoint m_mouseDownPos;
	Vec3 p0,p1,p2;
	int m_mode;

public:
	//////////////////////////////////////////////////////////////////////////
	CSolidBrushMouseCreateCallback( CSolidBrushObject *pObject )
	{
		m_mode = 0;
		m_pObject = pObject;
	}

	virtual	void Release() { delete this; };
	virtual	bool ContinueCreation() { return true; };

	//////////////////////////////////////////////////////////////////////////
	virtual	MouseCreateResult OnMouseEvent( CViewport *view,EMouseEvent event,CPoint &point,int flags )
	{
		bool bNeedOnlyOnceClick = false;
		if( CSolidBrushObject::s_nCreateType == SBrushCommon::eBrushCreateType_Sphere ||
				CSolidBrushObject::s_nCreateType == SBrushCommon::eBrushCreateType_Plane )
		{
			bNeedOnlyOnceClick = true;
		}

		if (event == eMouseRDblClick)
			return MOUSECREATE_ABORT;

		if (event == eMouseMove || event == eMouseLDown || event == eMouseLUp)
		{
			if (m_mode == 0)
			{
				p0 = view->MapViewToCP( point );
				{
					SW_IGNORE_CHANGES_AUTOGUARD;
					m_pObject->SetPos( p0 );
				}
				if (event == eMouseLDown)
				{
					SW_TEST_OBJ_PLACETO_MCB(m_pObject->GetPos(), m_pObject->GetLayer(), false);
					view->SetConstructionOrigin(p0);
					m_mouseDownPos = point;
					m_mode = 1;
				}
				return MOUSECREATE_CONTINUE;
			}
			if (m_mode == 1)
			{
				p0 = view->MapViewToCP( m_mouseDownPos,AXIS_XY );
				p1 = view->MapViewToCP( point,AXIS_XY );
				p1.z = p0.z + 0.01f;
				if (event == eMouseLUp)
				{
					SW_TEST_OBJ_PLACETO_MCB(m_pObject->GetPos(), m_pObject->GetLayer(), false);
					m_mouseDownPos = point;
					view->SetConstructionOrigin(p1);
					m_mode = 2;
				}
			}

			if (m_mode == 2)
			{
				Vec3 src = view->MapViewToCP( m_mouseDownPos,AXIS_Z );
				Vec3 trg = view->MapViewToCP( point,AXIS_Z );
				Vec3 dir = view->GetCPVector(src,trg,AXIS_Z);
				if( bNeedOnlyOnceClick )
				{
					p1.z = p0.z + 0.01f;
				}
				else
				{
					p1.z = p0.z + dir.z;
					p1 = view->SnapToGrid( p1 );
				}
			}

			AABB brushBox;
			brushBox.Reset();
			brushBox.Add( p0 );
			brushBox.Add( p1 );

			bool bSolidValid = false;

			// If width or height or depth are zero.
			if(	fabs(brushBox.min.x-brushBox.max.x) > 0.0001f &&
					fabs(brushBox.min.y-brushBox.max.y) > 0.0001f &&
					fabs(brushBox.min.z-brushBox.max.z) > 0.0001f	)
			{
				Vec3 center = (brushBox.min + brushBox.max)/2.0f;
				brushBox.min -= center;
				brushBox.max -= center;
				m_pObject->SetPos( center );
				bSolidValid = m_pObject->CreateBrush( brushBox,
																							CSolidBrushObject::s_nCreateType,
																							CSolidBrushObject::s_nCreateNumSides );
			}

			if( m_mode == 2 &&	( event == eMouseLDown || ( event == eMouseLUp && bNeedOnlyOnceClick ) ) )
			{
				if( bSolidValid )
				{
					SW_TEST_OBJ_PLACETO_MCB(m_pObject->GetPos(), m_pObject->GetLayer(), true);
					SW_ON_OBJ_NEW(m_pObject);
					return MOUSECREATE_OK;
				}
				else
					return MOUSECREATE_ABORT;
			}
		}
		return MOUSECREATE_CONTINUE;
	}	
};

//////////////////////////////////////////////////////////////////////////
// CSolidBrushObject implementation.
//////////////////////////////////////////////////////////////////////////
int CSolidBrushObject::s_nCreateNumSides = 4;
uint32 CSolidBrushObject::s_nGlobalBrushFileId = 0;
SBrushCommon::ESolidBrushCreateType CSolidBrushObject::s_nCreateType = SBrushCommon::eBrushCreateType_Box;

CSolidBrushObject::CSolidBrushObject()
{
	m_pRenderNode = 0;
	m_renderFlags = ERF_CASTSHADOWMAPS;

	m_viewDistRatio = 100;
	m_lightmapQuality = 1;

	SetColor( RGB(0,255,255) );
	m_bbox.min.Set(0,0,0);
	m_bbox.max.Set(0,0,0);

	m_bIgnoreNodeUpdate = false;
	m_elemType = SO_ELEM_NONE;

	m_nBrushUniqFileId = s_nGlobalBrushFileId;
	++s_nGlobalBrushFileId;

	m_BackupScale = Vec3(1,1,1);

	UseMaterialLayersMask(true);
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::Done()
{
	if (m_pRenderNode)
	{
		if(m_pRenderNode->GetRenderNodeType()>=0)
			GetIEditor()->Get3DEngine()->DeleteRenderNode(m_pRenderNode);
		m_pRenderNode = 0;
	}

	//free brush.
	m_Brush = 0;

	CBaseObject::Done();
}

//////////////////////////////////////////////////////////////////////////
bool CSolidBrushObject::Init( IEditor *ie,CBaseObject *prev,const CString &file )
{
	if (prev)
	{
		CSolidBrushObject *brushObj = static_cast<CSolidBrushObject*>(prev);
		SBrush* pBrush = NULL;
		if (brushObj->GetBrush(pBrush))
		{
			_smart_ptr<SBrush> brush;
			pBrush->Clone(brush);
			SetBrush(brush);
		}
	}

	// Must be after SetBrush call.
	bool res = CBaseObject::Init( ie,prev,file );	
	if (prev)
	{
		CSolidBrushObject *brushObj = static_cast<CSolidBrushObject*>(prev);
		m_bbox = brushObj->m_bbox;
		m_renderFlags = brushObj->m_renderFlags;
		m_brushFlags = brushObj->m_brushFlags;
		m_viewDistRatio = brushObj->m_viewDistRatio;
		m_lightmapQuality = brushObj->m_lightmapQuality;
		m_invertTM = brushObj->m_invertTM;

	}

	return res;
}

//////////////////////////////////////////////////////////////////////////
bool CSolidBrushObject::CreateGameObject()
{
	if (!m_pRenderNode)
	{
		m_pRenderNode = GetIEditor()->Get3DEngine()->CreateRenderNode( eERType_Brush );
		m_pRenderNode->SetEditorObjectId( GetId().Data1 );
		//OnRenderVarChange(0);
		UpdateEngineNode();
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::BeginEditParams( IEditor *ie,int flags )
{
	CBaseObject::BeginEditParams( ie,flags );

	if (flags & OBJECT_CREATE)
	{
		if (!s_brushPanelCreate)
		{
			s_brushPanelCreate = new CSolidBrushCreatePanel();
			s_brushPanelCreateId = ie->AddRollUpPage( ROLLUP_OBJECTS,_T("Create Solid Parameters"),s_brushPanelCreate );
		}
	}

	if (!s_brushPanelFlags)
	{
		s_brushPanelFlags = new CSolidFlagsPanelUI();
		s_brushPanelFlagsId = ie->AddRollUpPage( ROLLUP_OBJECTS,_T("Geometry Flags"),s_brushPanelFlags );
	}
	s_brushPanelFlags->SetMultiSelect(false);
	s_brushPanelFlags->SetObject(this);

	if (!s_brushPanel)
	{
		s_brushPanel = new CSolidBrushPanel();
		s_brushPanelId = ie->AddRollUpPage( ROLLUP_OBJECTS,_T("Solid Parameters"),s_brushPanel );
	}
	if (s_brushPanel)
	{
		s_brushPanel->SetBrush( this );
	}

}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::EndEditParams( IEditor *ie )
{
	CBaseObject::EndEditParams( ie );

	if (s_brushPanelCreate)
	{
		ie->RemoveRollUpPage( ROLLUP_OBJECTS,s_brushPanelCreateId );
		s_brushPanelCreate = 0;
		s_brushPanelCreateId = 0;
	}

	if (s_brushPanel)
	{
		ie->RemoveRollUpPage( ROLLUP_OBJECTS,s_brushPanelId );
		s_brushPanel = 0;
		s_brushPanelId = 0;
	}

	if (s_brushPanelFlags)
	{
		ie->RemoveRollUpPage( ROLLUP_OBJECTS,s_brushPanelFlagsId );
		s_brushPanelFlags = 0;
		s_brushPanelFlagsId = 0;
	}
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::BeginEditMultiSelParams( bool bAllOfSameType )
{
	CBaseObject::BeginEditMultiSelParams( bAllOfSameType );
	if (bAllOfSameType)
	{
		if (!s_brushPanelFlags)
		{
			s_brushPanelFlags = new CSolidFlagsPanelUI();
			s_brushPanelFlags->AddVariables();
			s_brushPanelFlagsId = GetIEditor()->AddRollUpPage( ROLLUP_OBJECTS,_T("Geometry Flags"),s_brushPanelFlags );
		}
		s_brushPanelFlags->SetMultiSelect(true);

		if (!s_brushPanel)
		{
			s_brushPanel = new CSolidBrushPanel;
			s_brushPanelId = GetIEditor()->AddRollUpPage( ROLLUP_OBJECTS,_T("Solid Parameters"),s_brushPanel );
		}
		if (s_brushPanel)
		{
			s_brushPanel->SetBrush( 0 );
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::EndEditMultiSelParams()
{
	CBaseObject::EndEditMultiSelParams();

	if (s_brushPanelFlags)
	{
		GetIEditor()->RemoveRollUpPage( ROLLUP_OBJECTS,s_brushPanelFlagsId );
		s_brushPanelFlags = 0;
		s_brushPanelFlagsId = 0;
	}
	if (s_brushPanel)
	{
		GetIEditor()->RemoveRollUpPage( ROLLUP_OBJECTS,s_brushPanelId );
		s_brushPanel = 0;
		s_brushPanelId = 0;
	}
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::EnableEditParams(bool bEnable)
{
	CBaseObject::EnableEditParams(bEnable);
	if (s_brushPanelCreate)
		s_brushPanelCreate->SetEnable(bEnable);
	if (s_brushPanelFlags)
		s_brushPanelFlags->SetEnable(bEnable);
	if (s_brushPanel)
		s_brushPanel->SetEnable(bEnable);
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::EnableEditMultiSelParams(bool bEnable, bool bAllOfSameType)
{
	CBaseObject::EnableEditMultiSelParams(bEnable, bAllOfSameType);
	if (s_brushPanelFlags)
		s_brushPanelFlags->SetEnableMultiSel(bEnable, bAllOfSameType);
	if (s_brushPanel)
		s_brushPanel->SetEnableMultiSel(bEnable, bAllOfSameType);
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SetSelected( bool bSelect )
{
	CBaseObject::SetSelected( bSelect );

	if (m_pRenderNode)
	{
		if (bSelect && gSettings.viewports.bHighlightSelectedGeometry)
			m_renderFlags |= ERF_SELECTED;
		else
			m_renderFlags &= ~ERF_SELECTED;
		m_pRenderNode->SetRndFlags( m_renderFlags );
	}
	if (m_Brush)
		m_Brush->SetSelected( bSelect );
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::GetBoundBox( AABB &box )
{
	box.SetTransformedAABB( GetWorldTM(),m_bbox );
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::GetLocalBounds( AABB &box )
{
	box = m_bbox;
}

//////////////////////////////////////////////////////////////////////////
IMouseCreateCallback* CSolidBrushObject::GetMouseCreateCallback()
{
	return new CSolidBrushMouseCreateCallback(this);
};

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::Display( DisplayContext &dc )
{
	if (dc.flags & DISPLAY_2D)
	{
		if (IsSelected())
			//dc.SetSelectedColor();
			dc.SetColor( RGB(225,0,0) );
		else
			dc.SetColor( GetColor() );
		if (m_Brush)
			m_Brush->Display( dc );
		return;
	}
	else
	{
		//dc.SetColor( GetColor() );
		//dc.PushMatrix( GetWorldTM() );
		//dc.DrawWireBox( m_bbox.min,m_bbox.max );
		//dc.PopMatrix();
	}

	dc.SetColor( GetColor() );
	if (m_Brush)
		m_Brush->Display( dc );

	/*
	if (m_Brush != 0 && m_indoor != 0)
	{
	if (!(m_Brush->m_flags&BRF_RE_VALID))
	{
	m_indoor->UpdateObject( m_Brush->GetIndoorGeom() );
	m_indoor->RecalcBounds();
	}
	Vec3 invCamSrc = dc.camera->GetPos();
	invCamSrc = m_invertTM.TransformPoint(invCamSrc);

	m_Brush->Render( dc.renderer,invCamSrc )
	}
	*/

	if (IsSelected() && !m_bbox.IsEmpty())
	{
		dc.SetSelectedColor();
		dc.PushMatrix( GetWorldTM() );
		dc.DrawWireBox( m_bbox.min,m_bbox.max );
		//dc.SetColor( RGB(255,255,0),0.1f ); // Yellow selected color.
		//dc.DrawSolidBox( m_bbox.min,m_bbox.max );

		float fScreenScale = dc.view->GetScreenScaleFactor(GetWorldTM().GetTranslation());

		// Display bounding box dimensions.
		float ofs = 0.02f * fScreenScale;
		Vec3 b0(m_bbox.min.x,m_bbox.max.y,m_bbox.min.z);
		Vec3 b1(m_bbox.max.x,m_bbox.max.y,m_bbox.min.z);
		Vec3 b2(m_bbox.max.x,m_bbox.min.y,m_bbox.min.z);
		Vec3 b3(m_bbox.max.x,m_bbox.max.y,m_bbox.max.z);
		Vec3 p0 = b0 + Vec3(0,ofs,0);
		Vec3 p1 = b1 + Vec3(ofs,ofs,0);
		Vec3 p2 = b2 + Vec3(ofs,0,0);
		Vec3 p3 = b3 + Vec3(ofs,ofs,0);

		float fArrowScale = 0.02f * fScreenScale;

		dc.SetColor( RGB(255,255,255),0.3f );
		dc.DrawArrow(p0,p1,fArrowScale,true);
		dc.DrawArrow(p1,p2,fArrowScale,true);
		dc.DrawArrow(p1,p3,fArrowScale,true);

		Vec3 vLocalScale = GetScale();
		AABB box = m_bbox;
		box.min.x *= vLocalScale.x;
		box.min.y *= vLocalScale.y;
		box.min.z *= vLocalScale.z;
		box.max.x *= vLocalScale.x;
		box.max.y *= vLocalScale.y;
		box.max.z *= vLocalScale.z;

		float fTextScale = 1.3f;
		dc.SetColor( RGB(200,200,200) );		
		char str[32];

		const ColorF TextBoxColor(0,0,0,0.75f);

		sprintf( str,"%.2f",box.max.x-box.min.x );
		DrawTextOn2DBox( dc, (p0+p1)*0.5f, str, fTextScale, TextBoxColor );

		sprintf( str,"%.2f",box.max.y-box.min.y );
		DrawTextOn2DBox( dc, (p1+p2)*0.5f, str, fTextScale, TextBoxColor );

		sprintf( str,"%.2f",box.max.z-box.min.z );
		DrawTextOn2DBox( dc, (p1+p3)*0.5f, str, fTextScale, TextBoxColor );

		dc.PopMatrix();

		if(m_elemType==SO_ELEM_VERTEX)
		{
			if(IsSelected())
				dc.DepthTestOff();

			dc.SetSelectedColor();
			const Matrix34 &wtm = GetWorldTM();
			float fPointSize = 0.5f;
			for(size_t f=0; f<m_Brush->GetNumberOfFaces(); ++f)
			{	
				if (!m_Brush->IsValidFace(f))
				{
					continue;
				}
				for (int i = 0; i < m_Brush->GetNumberOfFacePoints(f); ++i)
				{	
					Vec3 p = wtm.TransformPoint(m_Brush->GetFacePointPos(f,i));
					float fScale = fPointSize*dc.view->GetScreenScaleFactor(p) * 0.01f;
					Vec3 sz(fScale,fScale,fScale);
					dc.DrawWireBox( p-sz,p+sz );
				}
			}
			if(IsSelected())
				dc.DepthTestOn();
		}
	}

	if (IsHighlighted() && m_Brush && gSettings.viewports.bHighlightMouseOverGeometry)
	{
		IStatObj *pStatObj = NULL;
		if( m_Brush->GetIStatObj(pStatObj) )
		{
			SGeometryDebugDrawInfo dd;
			dd.tm = GetWorldTM();
			dd.color = ColorB(250,0,250,30);
			dd.lineColor = ColorB(255,255,0,160);
			dd.bExtrude = true;
			pStatObj->DebugDraw( dd );
		}
	}

	DrawDefault( dc );
}

//////////////////////////////////////////////////////////////////////////
XmlNodeRef CSolidBrushObject::Export( const CString &levelPath,XmlNodeRef &xmlNode )
{
	return 0;
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::Serialize( CObjectArchive &ar )
{
	XmlNodeRef xmlNode = ar.node;
	m_bIgnoreNodeUpdate = true;
	CBaseObject::Serialize( ar );
	m_bIgnoreNodeUpdate = false;
	if (ar.bLoading)
	{
		ar.node->getAttr( "RndFlags",m_renderFlags );
		ar.node->getAttr( "ViewDistRatio",m_viewDistRatio );
		ar.node->getAttr( "LMQuality",m_lightmapQuality );
		XmlNodeRef brushNode = xmlNode->findChild( "Brush" );
		if (brushNode)
		{
			_smart_ptr<SBrush> brush = m_Brush;
			if( !brush )
			{
				brush = SBrush::CreateBrush(this);
			}
			brush->Serialize( brushNode,ar.bLoading,ar.bUndo );

			if (!m_Brush)
				SetBrush( brush );
		}

		if (ar.bUndo)
		{
			if( m_pRenderNode )
				GetIEditor()->Get3DEngine()->DeleteRenderNode(m_pRenderNode);
			m_pRenderNode = GetIEditor()->Get3DEngine()->CreateRenderNode(eERType_Brush);
			m_pRenderNode->SetEditorObjectId(GetId().Data1);
			UpdateEngineNode();
			InvalidateBrush();
		}
		else
		{
			UpdateEngineNode();
		}
	}
	else
	{
		ar.node->setAttr( "RndFlags",m_renderFlags );
		ar.node->setAttr( "ViewDistRatio",m_viewDistRatio );
		ar.node->setAttr( "LMQuality",m_lightmapQuality );
		if (m_Brush)
		{
			XmlNodeRef brushNode = xmlNode->newChild( "Brush" );
			m_Brush->Serialize( brushNode,ar.bLoading,ar.bUndo );
		}
	}
}

//////////////////////////////////////////////////////////////////////////
bool CSolidBrushObject::HitTest( HitContext &hc )
{
	if (!m_Brush)
		return false;

	// Recreate the brush if editing, for hit-testing.
	if( CheckFlags( OBJFLAG_SUBOBJ_EDITING ) )
		InvalidateBrush();

	m_Brush->HitTest( hc );
	Vec3 pnt;

	Vec3 localRaySrc = hc.raySrc;
	Vec3 localRayDir = hc.rayDir;
	WorldToLocalRay( localRaySrc,localRayDir );

	bool bHit = false;

	if (Intersect::Ray_AABB( localRaySrc,localRayDir,m_bbox,pnt ))
	{
		Vec3 prevSrc = hc.raySrc;
		Vec3 prevDir = hc.rayDir;
		hc.raySrc = localRaySrc;
		hc.rayDir = localRayDir;

		if (m_Brush)
			bHit = m_Brush->HitTest(hc);

		hc.raySrc = prevSrc;
		hc.rayDir = prevDir;

		// The local hit distance should be transformed into world reference
		// in case of local coordinate being scaled.
		Vec3 localHitPos = localRaySrc + localRayDir*hc.dist;
		Vec3 worldHitPos = GetWorldTM().TransformPoint(localHitPos);
		hc.dist = (worldHitPos-hc.raySrc).len();
	}
	return bHit;
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SetBrush(  SBrush* brush  )
{
	if (m_Brush == brush)
		return;

	m_Brush = brush;
	if (m_Brush)
	{
		m_Brush->SetMatrix( GetWorldTM() );
		m_Brush->SetOwner(this);
		UpdateEngineNode();
		m_bbox = m_Brush->GetBoundBox();		
	}
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::InvalidateBrush()
{
	if (m_Brush)
	{
		m_Brush->Invalidate();
		m_Brush->SetMatrix( GetWorldTM() );
		m_Brush->GetBounds( m_bbox );
		UpdateEngineNode();
		if (m_pRenderNode)
			m_pRenderNode->Physicalize();
	}
}

//! Retrieve brush assigned to object.
bool CSolidBrushObject::GetBrush(SBrush*& pOutBrush) const
{
	pOutBrush = m_Brush;
	return pOutBrush ? true : false;
}


//////////////////////////////////////////////////////////////////////////
//! Invalidates cached transformation matrix.
void CSolidBrushObject::InvalidateTM( int nWhyFlags )
{
	CBaseObject::InvalidateTM(nWhyFlags);

	if (m_Brush)
	{
		m_Brush->SetMatrix( GetWorldTM() );
	}

	if (m_pRenderNode)
		UpdateEngineNode(true);

	m_invertTM = GetWorldTM();
	m_invertTM.Invert();
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::WorldToLocalRay( Vec3 &raySrc,Vec3 &rayDir )
{
	raySrc = m_invertTM.TransformPoint( raySrc );
	rayDir = m_invertTM.TransformVector(rayDir).GetNormalized();
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SelectBrushSide( const Vec3 &raySrc,const Vec3 &rayDir,bool shear )
{
	if (!m_Brush)
		return;

	m_subSelection.Clear();

	Vec3 rSrc = raySrc;
	Vec3 rDir = rayDir;
	Vec3 rTrg;
	WorldToLocalRay( rSrc,rDir );
	rTrg = rSrc + rDir*32768.0f;

	m_Brush->SelectSide( rSrc,rDir,shear,m_subSelection );
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::MoveSelectedPoints( const Vec3 &worldOffset )
{
	if (!m_Brush)
		return;

	// Store undo.
	StoreUndo( "Stretch Brush" );

	AABB prevBounds = m_Brush->GetBoundBox();

	std::vector<Vec3> prevPoints;
	prevPoints.resize( m_subSelection.points.size() );

	const Matrix34 &tm = GetWorldTM();

	//CHANGED_BY_IVO
	Vec3 ofs = m_invertTM.TransformVector( worldOffset );

	for (int i = 0; i < m_subSelection.points.size(); ++i)
	{
		Vec3 pnt = *m_subSelection.points[i];
		prevPoints[i] = pnt; // Remember previous point.
		pnt = m_invertTM.TransformPoint(pnt) + ofs;
		*m_subSelection.points[i] = tm.TransformPoint(pnt);
	}

	// Now optimize brush if it correctly created.
	// Now move brush. to make position center of the brush again.
	//Vec3 halfSize = (m_Brush->m_bounds.max - m_Brush->m_bounds.min)/2;
	Vec3 prevMid = (prevBounds.max + prevBounds.min)/2;
	Vec3 mid = (m_Brush->GetBoundBox().max + m_Brush->GetBoundBox().min)/2;
	ofs = mid - prevMid;
	m_Brush->Move( -ofs );

	SetPos( GetPos() + GetWorldTM().TransformVector(ofs) );

	m_bbox = m_Brush->GetBoundBox();
}

//////////////////////////////////////////////////////////////////////////
bool CSolidBrushObject::CreateBrush( const AABB &bbox,SBrushCommon::ESolidBrushCreateType createType, int numSides )
{	
	if (!IsEquivalent(bbox.min,bbox.max))
	{
		_smart_ptr<SBrush> pBrush = SBrush::CreateBrush(this);		
		pBrush->Create( bbox.min, bbox.max, numSides, createType );		
		SetBrush( pBrush );		
		m_bbox = pBrush->GetBoundBox();
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::OnRenderVarChange( IVariable *var )
{
	UpdateEngineNode();
}

//////////////////////////////////////////////////////////////////////////
IPhysicalEntity* CSolidBrushObject::GetCollisionEntity() const
{
	// Returns physical object of entity.
	if (m_pRenderNode)
		return m_pRenderNode->GetPhysics();
	return 0;
}

//////////////////////////////////////////////////////////////////////////
bool CSolidBrushObject::ConvertFromObject( CBaseObject *object )
{
	return false;
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SetRenderFlags( int nRndFlags )
{
	m_renderFlags = nRndFlags;
	if (m_pRenderNode)
		m_pRenderNode->SetRndFlags( m_renderFlags );
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SetViewDistRatio( int nRatio )
{
	m_viewDistRatio = nRatio;
	if (m_pRenderNode)
		m_pRenderNode->SetViewDistRatio( m_viewDistRatio );
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::UpdateEngineNode( bool bOnlyTransform )
{
	if (m_bIgnoreNodeUpdate)
		return;

	if (!m_pRenderNode)
		return;

	if (IsSelected() && gSettings.viewports.bHighlightSelectedGeometry)
		m_renderFlags |= ERF_SELECTED;
	else
		m_renderFlags &= ~ERF_SELECTED;

	m_pRenderNode->SetRndFlags( m_renderFlags );
	m_pRenderNode->SetViewDistRatio( m_viewDistRatio );
	m_pRenderNode->SetMinSpec( GetMinSpec() );
	m_pRenderNode->SetMaterialLayers( GetMaterialLayersMask() );
	m_renderFlags = m_pRenderNode->GetRndFlags();

	/*
	if (m_prefabGeom)
	{
	Matrix34 tm = GetBrushMatrix();
	m_pRenderNode->SetEntityStatObj( 0,m_prefabGeom->GetGeometry(),&tm );
	}
	*/

	if (m_Brush)
	{
		IStatObj *pGeom = NULL;
		if( m_Brush->GetIStatObj(pGeom) )
		{

			pGeom->SetFilePath( GenerateGameFilename() );
			Matrix34A mtx = GetWorldTM();
			m_pRenderNode->SetEntityStatObj( 0,pGeom,&mtx );
		}
	}

	// Fast exit if only transformation needs to be changed.
	if (bOnlyTransform)
		return;

	if (GetMaterial())
	{
		GetMaterial()->AssignToEntity( m_pRenderNode );
	}
	else
	{
		// Reset all material settings for this node.
		m_pRenderNode->SetMaterial(0);
	}
	// Set material can change render node flags.
	m_renderFlags = m_pRenderNode->GetRndFlags();
}

//////////////////////////////////////////////////////////////////////////
Matrix34 CSolidBrushObject::GetBrushMatrix() const
{
	return GetWorldTM();
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::UpdateVisibility( bool visible )
{
	CBaseObject::UpdateVisibility( visible );
	if (m_pRenderNode)
	{
		if (!visible || IsHiddenBySpec())
			m_renderFlags |= ERF_HIDDEN;
		else
			m_renderFlags &= ~ERF_HIDDEN;
		m_pRenderNode->SetRndFlags( m_renderFlags );
		SBrush* pBrush = NULL;
		if( GetBrush(pBrush) )
		{
			IStatObj* pStatObj = NULL;
			if( pBrush->GetIStatObj(pStatObj) )
			{
				int flag = pStatObj->GetFlags();
				if( visible )
					flag  &= (~STATIC_OBJECT_HIDDEN);
				else
					flag |= STATIC_OBJECT_HIDDEN;
				pStatObj->SetFlags(flag);
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SetMaterial( CMaterial *mtl )
{
	CBaseObject::SetMaterial(mtl);
	if (m_pRenderNode)
		UpdateEngineNode();
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SetMaterial( const CString& materialName )
{
	CMaterial *pMaterial = NULL;
	if (!materialName.IsEmpty())
	{ 
		pMaterial = GetIEditor()->GetMaterialManager()->LoadMaterial( materialName, false );
		if ( !pMaterial || (pMaterial && pMaterial->IsDummy()) )  
		{
			IMaterial* pMatInfo = gEnv->p3DEngine->GetMaterialManager()->LoadMaterial(materialName,false);
			if (pMatInfo)
			{
				GetIEditor()->GetMaterialManager()->OnCreateMaterial(pMatInfo);					
			}
			else
			{
				pMatInfo = gEnv->p3DEngine->GetMaterialManager()->GetDefaultMaterial();
			}

			if( pMatInfo )
				pMaterial = GetIEditor()->GetMaterialManager()->FromIMaterial(pMatInfo);	
		}
	}
	SetMaterial(pMaterial);
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SetMaterialLayersMask( uint32 nLayersMask )
{
	CBaseObject::SetMaterialLayersMask(nLayersMask);
	if (m_pRenderNode)
		UpdateEngineNode();
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SetMinSpec( uint32 nSpec )
{
	__super::SetMinSpec(nSpec);
	if (m_pRenderNode)
	{
		m_pRenderNode->SetMinSpec( GetMinSpec() );
		m_renderFlags = m_pRenderNode->GetRndFlags();
	}
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::Validate( CErrorReport *report )
{
	CBaseObject::Validate( report );

	if (!m_Brush)
	{
		CErrorRecord err;
		err.error.Format( "Empty Solid Brush %s",(const char*)GetName() );
		err.pObject = this;
		report->ReportError(err);
	}
}

//////////////////////////////////////////////////////////////////////////
bool CSolidBrushObject::IsSimilarObject( CBaseObject *pObject )
{
	if (pObject->GetClassDesc() == GetClassDesc() && GetRuntimeClass() == pObject->GetRuntimeClass())
	{
		return true;
	}
	return false;
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::OnEvent( ObjectEvent event )
{
	CBaseObject::OnEvent( event );
	switch (event)
	{
	case EVENT_INGAME:
		{
			if( CheckFlags( OBJFLAG_SUBOBJ_EDITING ) )
			{
				EndSubObjectSelection();
			}
		}
		break;
	case EVENT_OUTOFGAME:
		if (m_Brush)
		{
			IStatObj *pStatObj = NULL;
			if( m_Brush->GetIStatObj(pStatObj) && (pStatObj->GetFlags() & STATIC_OBJECT_GENERATED))
			{
				// Stat object was modified in game, rebuild it.
				m_Brush->UpdateMesh();
				InvalidateBrush();
			}
		}
		break;
	case EVENT_HIDE_HELPER:
		if( IsSelected() && m_Brush )
		{
			IStatObj* obj = NULL;
			if( m_Brush->GetIStatObj(obj) )
			{
				int flag = obj->GetFlags();
				flag &= ~STATIC_OBJECT_HIDDEN;
				obj->SetFlags(flag);
			}
		}
		break;
	}
}

//////////////////////////////////////////////////////////////////////////
bool CSolidBrushObject::StartSubObjSelection( int elemType )
{
	m_elemType=elemType;
	bool bStarted = false;
	if (m_Brush)
		bStarted = m_Brush->StartSubObjSelection( GetWorldTM(),elemType,0 );
	if (bStarted)
		SetFlags(OBJFLAG_SUBOBJ_EDITING);

	if (m_pRenderNode != 0 && IsSelected())
	{
		m_renderFlags &= ~ERF_SELECTED;
		m_pRenderNode->SetRndFlags( m_renderFlags );
	}

	if (bStarted)
	{
		if (!s_brushPanelSubObjId)
		{
			s_brushPanelSubObj = new CSolidBrushSubObjPanel();
			s_brushPanelSubObj->SetObject(this);
			s_brushPanelSubObjId = GetIEditor()->AddRollUpPage( ROLLUP_OBJECTS,_T("Sub Object Edit"),s_brushPanelSubObj );
		}
	}

	return bStarted;
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::EndSubObjectSelection()
{
	m_elemType = SO_ELEM_NONE;
	if (m_pRenderNode != 0 && IsSelected() && gSettings.viewports.bHighlightSelectedGeometry)
	{
		m_renderFlags |= ERF_SELECTED;
		m_pRenderNode->SetRndFlags( m_renderFlags );
	}
	ClearFlags(OBJFLAG_SUBOBJ_EDITING);
	if (m_Brush)
	{
		PivotToCenter();
		m_Brush->EndSubObjSelection();
		InvalidateBrush();
	}
	if (s_brushPanelSubObjId)
	{
		GetIEditor()->RemoveRollUpPage(ROLLUP_OBJECTS,s_brushPanelSubObjId);
		s_brushPanelSubObjId = 0;
		s_brushPanelSubObj = 0;
	}
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::CalculateSubObjectSelectionReferenceFrame( ISubObjectSelectionReferenceFrameCalculator* pCalculator )
{
	if (m_Brush)
		pCalculator->AddBrush(this->GetWorldTM(), m_Brush);
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::ModifySubObjSelection( SSubObjSelectionModifyContext &modCtx )
{
	if (CUndo::IsRecording())
		CUndo::Record( new CUndoSolidBrushObject(this, m_Brush,"Brush SubObj Modify") );

	if (m_Brush)
	{
		m_Brush->ModifySelection( modCtx, false );
		m_Brush->GetBounds( m_bbox );
		UpdateEngineNode();
	}
	InvalidateTM(0);
	SW_ON_OBJ_MOD(this);
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::AcceptSubObjectModify()
{
	if (m_Brush)
	{
		m_Brush->AcceptModifySelection();
		m_Brush->GetBounds( m_bbox );
		UpdateEngineNode();
	}
	SW_ON_OBJ_MOD(this);
}

//////////////////////////////////////////////////////////////////////////
CEdGeometry* CSolidBrushObject::GetGeometry()
{
	// Return our geometry.
	return m_Brush;
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::ResetTransform()
{
	if (!m_Brush)
		return;

	Vec3 relativePos = m_Brush->GetBoundBox().GetCenter();
	Vec3 newPos = m_Brush->GetMatrix().GetTranslation() + relativePos;
	SW_TEST_OBJ_MOVETO(this, newPos, GetLayer(), false);
	
	StoreUndo( "Reset Transform" );

	 // Reset brush transformation.
	Matrix34 brushTM = GetWorldTM();
	brushTM.SetTranslation(Vec3(0,0,0));
	m_Brush->Transform(brushTM);
	m_bbox = m_Brush->GetBoundBox();

	Vec3 newPivot;
	m_Brush->PivotToCenter(&newPivot);

	Matrix34 worldTM;
	worldTM.SetIdentity();
	worldTM.SetTranslation(newPivot);

	SetWorldTM(worldTM);
	m_Brush->SetMatrix(GetWorldTM());

	InvalidateBrush();
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SnapPointsToGrid()
{
	StoreUndo( "Snap to Grid" );

	if (m_Brush)
		m_Brush->SnapToGrid();
	InvalidateBrush();
	SW_ON_OBJ_MOD(this);
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::SaveToCgf( const CString filename )
{
	IStatObj* pObj = NULL;
	if( m_Brush != 0 && m_Brush->GetIStatObj(pObj) )
	{
		pObj->SaveToCGF( filename, NULL, true );
	}
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::RegisterCommands( CRegistrationContext &rc )
{
	rc.pCommandManager->RegisterCommand( "Brush.ResetTransform",functor(&CSolidBrushObject::Command_ResetTransform) );
	rc.pCommandManager->RegisterCommand( "Brush.SnapPointsToGrid",functor(&CSolidBrushObject::Command_SnapPointsToGrid) );	
	rc.pCommandManager->RegisterCommand( "Brush.MergeBrush",functor(&CSolidBrushObject::Command_MergeBrush) );
	rc.pCommandManager->RegisterCommand( "Brush.CSGUnionBrush",functor(&CSolidBrushObject::Command_CSGUnionBrush) );
	rc.pCommandManager->RegisterCommand( "Brush.CSGIntersectionBrush",functor(&CSolidBrushObject::Command_CSGIntersectionBrush) );
	rc.pCommandManager->RegisterCommand( "Brush.CSGDifferenceBrush",functor(&CSolidBrushObject::Command_CSGDifferenceBrush) );
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::Command_ResetTransform()
{
	CUndo undo("Brush Reset Transform");
	CSelectionGroup *pSel = ::GetIEditor()->GetSelection();
	for (int i = 0; i < pSel->GetCount(); ++i)
	{
		if (pSel->GetObject(i)->IsKindOf(RUNTIME_CLASS(CSolidBrushObject)))
		{
			CSolidBrushObject *pSolid = static_cast<CSolidBrushObject*>(pSel->GetObject(i));
			pSolid->ResetTransform();
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::Command_SnapPointsToGrid()
{
	CUndo undo("Brush Snap Points To Grid");
	CSelectionGroup *pSel = ::GetIEditor()->GetSelection();
	for (int i = 0; i < pSel->GetCount(); ++i)
	{
		if (pSel->GetObject(i)->IsKindOf(RUNTIME_CLASS(CSolidBrushObject)))
		{
			CSolidBrushObject *pSolid = static_cast<CSolidBrushObject*>(pSel->GetObject(i));
			pSolid->SnapPointsToGrid();
		}
	}
}


void CSolidBrushObject::Command_MergeBrush()
{	
	CSelectionGroup *pSel = ::GetIEditor()->GetSelection();
	std::vector<CSolidBrushObject*> solidlist;

	for (int i = 0; i < pSel->GetCount(); ++i)
	{
		CBaseObject* obj = pSel->GetObject(i);
		if( obj && obj->IsKindOf(RUNTIME_CLASS(CSolidBrushObject)))
			solidlist.push_back((CSolidBrushObject*)obj);
	}

	if( solidlist.size() < 2 )
		return;

#ifdef SEG_WORLD
	for( size_t i = 0; i < solidlist.size(); ++i )
	{
		CSolidBrushObject* obj = solidlist[i];
		SW_TEST_OBJ_MOD(obj, true, false);
	}
	AABB box;
	box.Reset();
	for( size_t i = 0; i < solidlist.size(); ++i )
	{
		CSolidBrushObject* obj = solidlist[i];
		if(box.IsReset())
			obj->GetBoundBox(box);
		else
		{
			AABB tmpBox;
			obj->GetBoundBox(tmpBox);
			box.Add(tmpBox);
		}
	}
	Vec3 newPos = box.GetCenter();
	SW_TEST_OBJ_PLACETO(newPos, solidlist[0]->GetLayer(), true);
#endif

	CSolidBrushObject* newobj;
	{
		SW_IGNORE_CHANGES_AUTOGUARD;

		CUndo undo("Brush Merge");
		newobj = (CSolidBrushObject*)::GetIEditor()->NewObject( "Solid", "" );
		bool bFailed = false;	

		for( size_t i = 0; i < solidlist.size(); ++i )
		{
			CSolidBrushObject* obj = solidlist[i];
			if( newobj->MergeSolidObject(obj) == false )
			{ 
				::GetIEditor()->GetObjectManager()->DeleteObject( newobj );
				CryMessageBox( "Fail to merge brushes.", "Solid Error", 0x00000000L );
				return;
			}
		}

		newobj->InvalidateBrush();
	}

#ifdef SEG_WORLD
	for( size_t i = 0; i < solidlist.size(); ++i )
	{
		CSolidBrushObject* obj = solidlist[i];
		SW_ON_OBJ_DEL(obj);
	}
	SW_ON_OBJ_NEW(newobj);
#endif

	::GetIEditor()->GetObjectManager()->DeleteSelection();
	::GetIEditor()->GetObjectManager()->SelectObject(newobj);
}


void CSolidBrushObject::Command_CSGUnionBrush()
{	
	DoCSG( SBrushCommon::eCOE_Union );
}


void CSolidBrushObject::Command_CSGIntersectionBrush()
{
	DoCSG( SBrushCommon::eCOE_Intersection );
}


void CSolidBrushObject::Command_CSGDifferenceBrush()
{
	DoCSG( SBrushCommon::eCOE_Difference );
}


void CSolidBrushObject::DoCSG( SBrushCommon::ECSGOperationEnum csgoperation )
{	
	CSelectionGroup	* pSel = ::GetIEditor()->GetSelection();
	Vec3 pos(0,0,0);

	_smart_ptr<SBrush> prevBrush;
	_smart_ptr<SBrush> brush;

	std::vector<CSolidBrushObject*> solidlist;
	for (int i = 0; i < pSel->GetCount(); ++i)
	{
		CBaseObject* obj = pSel->GetObject(i);
		if( obj && obj->IsKindOf(RUNTIME_CLASS(CSolidBrushObject)) )
			solidlist.push_back((CSolidBrushObject*)obj);
	}

	if( solidlist.size() < 2 )
	{
		CryMessageBox( "More than 2 solids must be selected.", "SolidError", 0x00000000L );
		return;
	}

	if( csgoperation == SBrushCommon::eCOE_Difference && solidlist.size() != 2 )
	{
		CryMessageBox( "When difference mode, the number of selected solids must be 2.", "SolidError", 0x00000000L );
		return;
	}

#ifdef SEG_WORLD
	for( size_t i = 0; i < solidlist.size(); ++i )
	{
		CSolidBrushObject* obj = solidlist[i];
		SW_TEST_OBJ_MOD(obj, true, false);
	}
#endif

	SBrush* pBrush(NULL);
	if( !solidlist[0]->GetBrush(pBrush)	)
		return;
	prevBrush = pBrush;
	
	CBrushCSGCompiler csgCompiler;
	Vec3 newPivot;

	for( size_t i = 0; i < solidlist.size(); ++i )
	{
		CSolidBrushObject* obj = solidlist[i];
		
		if( !obj->GetBrush(pBrush) )
			continue;

		if( pBrush == prevBrush )
			continue;

		brush = SBrush::CreateBrush(NULL);

		if( csgCompiler.Compile(	csgoperation,
															prevBrush,
															pBrush,
															brush,
															newPivot ) == false )
		{
			CryMessageBox( "Sorry, Fail to compile CSG because there might be too much faces in a solid.", "SolidError", 0x00000000L );
			return;
		}

		prevBrush = brush;

		Matrix34 tm;
		tm.SetIdentity();
		tm.SetTranslation(newPivot);
		brush->SetMatrix(tm);
	}

	SW_TEST_OBJ_PLACETO(newPivot, solidlist[0]->GetLayer(), false);

	CSolidBrushObject* newobj;
	{
		SW_IGNORE_CHANGES_AUTOGUARD;

		string undostr;
		undostr.Format( "Brush CSG %s", SBrushCommon::CSGOperationString[csgoperation] );
		CUndo undo(undostr);

		newobj = (CSolidBrushObject*)::GetIEditor()->NewObject( "Solid", "" );
		newobj->SetBrush(brush);
		newobj->SetPos(newPivot);
		newobj->InvalidateBrush();
	}

#ifdef SEG_WORLD
	for( size_t i = 0; i < solidlist.size(); ++i )
	{
		CSolidBrushObject* obj = solidlist[i];
		SW_ON_OBJ_DEL(obj);
	}
	SW_ON_OBJ_NEW(newobj);
#endif
	
	::GetIEditor()->GetObjectManager()->DeleteSelection();
	::GetIEditor()->GetObjectManager()->SelectObject(newobj);
}


//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::PivotToVertices()
{
	StoreUndo( "PivotToVertices" );

	std::vector<Vec3*> points;

	for(size_t f=0; f<m_Brush->GetNumberOfFaces(); ++f)
	{	
		if(m_Brush->IsValidFace(f))
		{
			for(int p = 0; p<m_Brush->GetNumberOfFacePoints(f); ++p)
			{
				if(m_Brush->IsPointSelected(f,p))
					points.push_back(&(const_cast<Vec3&>(m_Brush->GetFacePointPos(f,p))));
			}
		}
	}

	if(points.size()==0)
		return;

	AABB bb(*points[0], *points[0]);
	for(int i=0; i<points.size(); ++i)
		bb.Add(*points[i]);

	Vec3 store = GetPos();
	Vec3 v = GetWorldTM().TransformPoint(bb.GetCenter());

	v = GetIEditor()->GetActiveView()->SnapToGrid( v );

	SW_TEST_OBJ_MOVETO(this, v, GetLayer(), true);

	Matrix34 m = GetWorldTM();
	m.SetTranslation(v);
	SetWorldTM(m);
	store = store - GetPos();

	m = GetWorldTM();
	m.SetTranslation(Vec3(0, 0, 0));
	Matrix34 invtm = m.GetInverted();
	store = invtm * store;

	InvalidateBrush();
}

//////////////////////////////////////////////////////////////////////////
void CSolidBrushObject::PivotToCenter()
{
	StoreUndo( "PivotToCenter" );	

	Matrix34 WorldTM = GetWorldTM();
	WorldTM.SetTranslation(Vec3(0,0,0));

	Matrix34 InvWorldTM = WorldTM.GetInverted();

	AABB aabb;
	aabb.Reset();
	for( size_t i = 0; i < m_Brush->GetNumberOfVertices(); ++i )
		aabb.Add( WorldTM * m_Brush->GetVertexPos(i) );	

	Vec3 RelativePivot = aabb.GetCenter();
	Vec3 TransformedRelativePivot = InvWorldTM.TransformPoint(RelativePivot);
	Vec3 newPivot = GetWorldTM().GetTranslation() + RelativePivot;
	
	SW_TEST_OBJ_MOVETO(this, newPivot, GetLayer(), true);

	for( size_t i = 0; i < m_Brush->GetNumberOfVertices(); ++i )
		m_Brush->SetVertexPos( i, m_Brush->GetVertexPos(i) - TransformedRelativePivot );	

	SetWorldPos(newPivot);
	m_Brush->SetMatrix(GetWorldTM());

	m_Brush->BuildBrush(false,false,false,true);
	InvalidateBrush();
}

//////////////////////////////////////////////////////////////////////////
CString CSolidBrushObject::GenerateGameFilename()
{
	char sId[128];
	itoa( m_nBrushUniqFileId,sId,10 );
	CString sRealGeomFileName = CString("%level%/Brush/") + sId + "." + CRY_GEOMETRY_FILE_EXT;
	return sRealGeomFileName;
}


bool CSolidBrushObject::MergeSolidObject( const CSolidBrushObject* solid )
{
	SBrush* pBrush=NULL;
	if( !solid->GetBrush(pBrush) )
		return false;

	if( m_Brush == NULL )
	{
		m_Brush = SBrush::CreateBrush(this);
		m_Brush->SetMatrix(pBrush->GetMatrix());
	}	

	Vec3 newPivot;
	if( m_Brush->MergeBrush(pBrush,&newPivot) == false )	
		return false;	

	Matrix34 WorldTM;
	WorldTM.SetIdentity();
	WorldTM.SetTranslation(newPivot);
	
	SetWorldTM(WorldTM);
	m_Brush->SetMatrix(WorldTM);

	return true;
}


void CSolidBrushObject::UpdateBoundBox()
{
	if( m_Brush )
		m_bbox = m_Brush->GetBoundBox();
}


void CSolidBrushObject::DrawTextOn2DBox( DisplayContext &dc, const Vec3& pos, const char* text, float textScale, const ColorF& TextBackColor )
{
	Vec3 worldPos = dc.ToWorldPos(pos);
	int vx, vy, vw, vh;::
	gEnv->pRenderer->GetViewport(&vx, &vy, &vw, &vh);

	const CCamera& camera = gEnv->pRenderer->GetCamera();
	Vec3 screenPos;
	camera.Project( worldPos, screenPos, Vec2i(0,0), Vec2i(0,0) );

	//! I want to know a way which get information about font such as font size.
	//! I tried to find interfaces related to it but I didn't look fot it yet. - jaesik
	int		textlen			= strlen(text);
	float fontsize		= 7.5f;
	float textwidth		= fontsize * textlen;
	float textheight	= 16.0f;

	screenPos.x = screenPos.x-textwidth*0.5f;

	Vec3 textregion[4] = {	Vec3( screenPos.x, screenPos.y, screenPos.z ),
													Vec3( screenPos.x + textwidth, screenPos.y, screenPos.z ),
													Vec3( screenPos.x + textwidth, screenPos.y + textheight, screenPos.z ),
													Vec3( screenPos.x, screenPos.y + textheight, screenPos.z )	 };

	Vec3 textworldreign[4];
	Matrix34 dcInvTm = dc.GetMatrix().GetInverted();	

	Matrix44A mProj, mView;
	mathMatrixPerspectiveFov(&mProj, camera.GetFov(), camera.GetProjRatio(), camera.GetNearPlane(), camera.GetFarPlane());
	mathMatrixLookAt(&mView, camera.GetPosition(), camera.GetPosition()+camera.GetViewdir(), Vec3(0, 0, 1));
	Matrix44A mInvViewProj = (mView*mProj).GetInverted();

	for( int i = 0; i < 4; ++i )
	{	
		Vec4 projectedpos = Vec4(	(textregion[i].x-vx)/vw*2.0f-1.0f, 
															-((textregion[i].y-vy)/vh)*2.0f+1.0f, 
															textregion[i].z, 
															1.0f );

		Vec4 wp = projectedpos * mInvViewProj;
		wp.x /= wp.w;
		wp.y /= wp.w;
		wp.z /= wp.w;
		textworldreign[i] = dcInvTm.TransformPoint(Vec3(wp.x,wp.y,wp.z));
	}

	ColorB backupcolor = dc.GetColor();	
	uint32 backupstate = dc.GetState();

	dc.SetState(backupstate|e_DepthTestOff);
	dc.SetColor(TextBackColor);
	dc.SetDrawInFrontMode(true);

	dc.DrawQuad(textworldreign[3], textworldreign[2], textworldreign[1], textworldreign[0]);

	dc.SetDrawInFrontMode(false);
	dc.SetColor(backupcolor);
	dc.SetState(backupstate);

	dc.DrawTextLabel(pos, textScale, text);
}


bool CSolidBrushObject::StartScaling()
{
	m_BackupScale = CBaseObject::GetScale();
	return true;
}


bool CSolidBrushObject::GetUntransformedScale( Vec3& scale ) const
{
	scale = m_BackupScale;
	return true;
}


bool CSolidBrushObject::TransformScale( const Vec3& scale )
{
	Matrix34 ScaleTM;	
	ScaleTM.SetScale(scale);
	m_Brush->Transform(ScaleTM,true);

	return true;
}

void CSolidBrushObject::GetVerticesInWorld(std::vector<Vec3>& vertices) const
{
	vertices.clear();
	vertices.reserve(m_Brush->GetNumberOfVertices());
	const Matrix34 &wtm = GetWorldTM();
	for(size_t v=0; v<m_Brush->GetNumberOfVertices(); ++v)
	{	
		vertices.push_back(wtm.TransformPoint(m_Brush->GetVertexPos(v)));
	}
}
